package s3v2

import (
	"context"
	"fmt"
	"io"
	"time"

	"gitee.com/Luna-CY/Golang-Project-Template/internal/configuration"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/context/contextutil"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/errors"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/logger"
	"gitee.com/Luna-CY/Golang-Project-Template/internal/util/cryptor"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

func New(conf configuration.StorageProvider) (*S3, errors.Error) {
	var options = make([]func(*config.LoadOptions) error, 0)

	if conf.S3V2.Region != "" {
		options = append(options, config.WithRegion(conf.S3V2.Region))
	}

	if conf.S3V2.Endpoint != "" {
		options = append(options, config.WithBaseEndpoint(conf.S3V2.Endpoint))
	}

	if conf.S3V2.AccessKey != "" && conf.S3V2.SecretKey != "" {
		options = append(options, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(conf.S3V2.AccessKey, conf.S3V2.SecretKey, "")))
	}

	s3c, err := config.LoadDefaultConfig(contextutil.NewContext(), options...)
	if err != nil {
		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.N_EW.371423 create s3v2 config failed: %v", err)

		return nil, errors.ErrorServerInternalError("ISSS_V2.N_EW.224531", "创建S3配置失败")
	}

	return &S3{
		config: conf,
		s3: s3.NewFromConfig(s3c, func(o *s3.Options) {
			o.UsePathStyle = !conf.S3V2.DisablePathStyle
		}),
	}, nil
}

type S3 struct {
	config configuration.StorageProvider

	s3 *s3.Client
}

func (cls *S3) GetName() string {
	return cls.config.Name
}

func (cls *S3) GetBucket() string {
	return cls.config.S3V2.Bucket
}

func (cls *S3) PutObject(ctx context.Context, filepath string, _ int64, reader io.Reader) errors.Error {
	var input = &s3.PutObjectInput{
		Bucket: aws.String(cls.config.S3V2.Bucket),
		Key:    aws.String(filepath),
		Body:   reader,
	}

	_, err := cls.s3.PutObject(ctx, input)
	if err != nil {
		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.S_S3.PO_CT.731414 put object failed: %v", err)

		return errors.ErrorServerInternalError("ISSS_V2.S_S3.PO_CT.521212", "上传对象失败")
	}

	return nil
}

func (cls *S3) GetObject(ctx context.Context, filepath string) (io.ReadCloser, errors.Error) {
	var input = &s3.GetObjectInput{
		Bucket: aws.String(cls.config.S3V2.Bucket),
		Key:    aws.String(filepath),
	}

	output, err := cls.s3.GetObject(ctx, input)
	if err != nil {
		var ae *types.NoSuchKey

		if errors.As(err, &ae) {
			return nil, errors.New(errors.ErrorTypeRecordNotFound, "ISSS_V2.S_S3.GO_CT.921827", "对象不存在")
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.S_S3.GO_CT.891405 get object failed: %v", err)

		return nil, errors.ErrorServerInternalError("ISSS_V2.S_S3.GO_CT.661245", "获取对象失败")
	}

	return output.Body, nil
}

func (cls *S3) DeleteObject(ctx context.Context, filepath string) errors.Error {
	var input = &s3.DeleteObjectInput{
		Bucket: aws.String(cls.config.S3V2.Bucket),
		Key:    aws.String(filepath),
	}

	_, err := cls.s3.DeleteObject(ctx, input)
	if err != nil {
		var ae *types.NoSuchKey

		if errors.As(err, &ae) {
			return errors.New(errors.ErrorTypeRecordNotFound, "ISSS_V2.S_S3.DE_CT.1151918", "对象不存在")
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.S_S3.DE_CT.1051355 delete object failed: %v", err)

		return errors.ErrorServerInternalError("ISSS_V2.S_S3.DE_CT.801254", "删除对象失败")
	}

	return nil
}

func (cls *S3) GetObjectAccessedUrl(ctx context.Context, filepath string, timeout time.Duration) (string, errors.Error) {
	if cls.config.Cdn.Enable {
		return fmt.Sprintf("%s/%s", cls.config.Cdn.Domain, filepath), nil
	}

	if cls.config.IsPublic {
		return fmt.Sprintf("%s/%s", cls.config.S3V2.Endpoint, filepath), nil
	}

	// 生成与签名访问链接
	var pc = s3.NewPresignClient(cls.s3)

	var input = &s3.GetObjectInput{
		Bucket: aws.String(cls.config.S3V2.Bucket),
		Key:    aws.String(filepath),
	}

	output, err := pc.PresignGetObject(ctx, input, func(o *s3.PresignOptions) {
		o.Expires = timeout
	})

	if err != nil {
		var ae *types.NoSuchKey

		if errors.As(err, &ae) {
			return "", errors.New(errors.ErrorTypeRecordNotFound, "ISSS_V2.S_S3.GOAU_RL.1511959.", "对象不存在")
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.S_S3.GOAU_RL.1542013 generate signed url failed: %v", err)

		return "", errors.ErrorServerInternalError("ISSS_V2.S_S3.GOAU_RL.1562019", "生成与签名访问链接失败")
	}

	return output.URL, nil
}

func (cls *S3) GetObjectHash(ctx context.Context, filepath string) (string, string, string, errors.Error) {
	var input = &s3.GetObjectInput{
		Bucket: aws.String(cls.config.S3V2.Bucket),
		Key:    aws.String(filepath),
	}

	object, rerr := cls.s3.GetObject(ctx, input)
	if rerr != nil {
		var ae *types.NoSuchKey

		if errors.As(rerr, &ae) {
			return "", "", "", errors.New(errors.ErrorTypeRecordNotFound, "ISSS_V2.S_S3.GOH_SH.1732030", "对象不存在")
		}

		logger.SugarLogger(logger.WithStack()).Errorf("ISSS_V2.S_S3.GOH_SH.1511305 get object failed: %v", rerr)

		return "", "", "", errors.ErrorServerInternalError("ISSS_V2.S_S3.GOH_SH.1531312", "获取对象哈希值失败")
	}

	defer object.Body.Close()

	md5, sha1, sha256, err := cryptor.SumFile(object.Body)
	if err != nil {
		return "", "", "", err.Relation(errors.ErrorServerInternalError("ISSS_V2.S_S3.GOH_SH.1581318", "获取对象哈希值失败"))
	}

	return md5, sha1, sha256, nil
}
